home *** CD-ROM | disk | FTP | other *** search
/ Delphi Magazine Collection 2001 / Delphi Magazine Collection 20001 (2001).iso / DISKS / Issue63 / Debug / HVBordebug / HVDll.pas < prev   
Encoding:
Pascal/Delphi Source File  |  1999-04-07  |  15.4 KB  |  594 lines

  1. unit HVDll;
  2. //
  3. // Support for DelayLoading of DLLs ß la VC++6.0
  4. // Written by Hallvard Vassbotn (hallvard@falcon.no), January 1999
  5. // Documentation in The Delphi Magazine, Febrary 1999 issue
  6. // Revised for use in BorDebug.Pas, March 1999
  7. //
  8. interface
  9.  
  10. uses
  11.   Windows,
  12.   Classes,
  13.   SysUtils;
  14.  
  15. //
  16. // Simple wrapper classes around the Win32 Heap functions.
  17. // Moved in from the HVHeaps unit
  18. //
  19. type
  20.   // The TPrivateHeap class gives basic memory allocation capability
  21.   // The benefit of using this class instead of the native GetMem
  22.   // and FreeMem routines, is that the memory pages used will
  23.   // be seperate from other allocations. This gives reduced
  24.   // fragmentation.
  25.   TPrivateHeap = class(TObject)
  26.   private
  27.     FHandle: THandle;
  28.     FAllocationFlags: DWORD;
  29.     function GetHandle: THandle;
  30.   public
  31.     constructor Create;
  32.     destructor Destroy; override;
  33.     procedure GetMem(var P{: pointer}; Size: DWORD); virtual;
  34.     procedure FreeMem(P: pointer);
  35.     function SizeOfMem(P: pointer): DWORD;
  36.     property Handle: THandle read GetHandle;
  37.     property AllocationFlags: DWORD read FAllocationFlags write FAllocationFlags;
  38.   end;
  39.  
  40.   // The Code Heap adds the feature of allocating readable/writable
  41.   // and executable memory blocks. This allows us to have safe
  42.   // run-time generated code while not wasting as much memory
  43.   // as calls to VirtualAlloc would have caused, while avoiding
  44.   // the pitfalls of changing the protection flags of blocks
  45.   // allocated with GetMem.
  46.   TCodeHeap = class(TPrivateHeap)
  47.   public
  48.     procedure GetMem(var P{: pointer}; Size: DWORD); override;
  49.   end;
  50.  
  51. type
  52.   // Structures to keep the address of function variables and name/id pairs
  53.   PPointer = ^pointer;
  54.   PEntry = ^TEntry;
  55.   TEntry = packed record
  56.     Proc: PPointer;
  57.     case integer of
  58.       0 : (Name: PChar);
  59.       1 : (ID  : Longint);
  60.     end;
  61.   PEntries = ^TEntries;
  62.   TEntries = packed array[0..High(Word)-1] of TEntry;
  63.  
  64.   // Structures to generate the per-routine thunks
  65.   PThunk = ^TThunk;
  66.   TThunk = packed record
  67.     CALL  : byte;
  68.     OFFSET: integer;
  69.   end;
  70.   PThunks = ^TThunks;
  71.   TThunks = packed array[0..High(Word)-1] of TThunk;
  72.  
  73.   // Structure to generate the per-DLL thunks
  74.   TThunkHeader = packed record
  75.     PUSH   : byte;
  76.     VALUE  : pointer;
  77.     JMP    : byte;
  78.     OFFSET : integer;
  79.   end;
  80.  
  81.   // The combined per-DLL and per-routine thunks
  82.   PThunkingCode = ^TThunkingCode;
  83.   TThunkingCode = packed record
  84.     ThunkHeader : TThunkHeader;
  85.     Thunks      : TThunks;
  86.   end;
  87.  
  88.   // The base class that provides DelayLoad capability
  89.   TDll = class(TObject)
  90.   private
  91.     FEntries  : PEntries;
  92.     FThunkingCode: PThunkingCode;
  93.     FCount    : integer;
  94.     FFullPath : string;
  95.     FHandle   : HMODULE;
  96.     function GetHandle: HMODULE;
  97.     procedure SetFullPath(const Value: string);
  98.     function GetProcs(Index: integer): pointer;
  99.     procedure SetProcs(Index: integer; Value: pointer);
  100.     function GetAvailable: boolean;
  101.     function GetLoaded: boolean;
  102.     function LoadProcAddrFromIndex(Index: integer; var Addr: pointer): boolean;
  103.     procedure ActivateThunks;
  104.     function GetEntryName(Index: integer): string;
  105.   protected
  106.     function LoadHandle: HMODULE; virtual;
  107.     class procedure Error(const Msg: string; Args: array of const);
  108.     procedure CreateThunks;
  109.     procedure DestroyThunks;
  110.     function HasThunk(Thunk: PThunk): boolean;
  111.     function GetProcAddrFromIndex(Index: integer): pointer;
  112.     function DelayLoadFromThunk(Thunk: PThunk): pointer; register;
  113.     function DelayLoadIndex(Index: integer): pointer;
  114.     function GetIndexFromThunk(Thunk: PThunk): integer;
  115.     function GetIndexFromProc(Proc: PPointer): integer;
  116.     function ValidIndex(Index: integer): boolean;
  117.     procedure CheckIndex(Index: integer);
  118.     property Procs[Index: integer]: pointer read GetProcs write SetProcs;
  119.   public
  120.     constructor Create(const DllName: string; const Entries: array of TEntry);
  121.     destructor Destroy; override;
  122.     procedure Load;
  123.     procedure Unload;
  124.     function HasRoutine(Proc: PPointer): boolean;
  125.     function HookRoutine(Proc: PPointer; HookProc: Pointer; var OrgProc{: Pointer}): boolean;
  126.     function UnHookRoutine(Proc: PPointer; var OrgProc{: Pointer}): boolean;
  127.     property FullPath: string read FFullPath write SetFullPath;
  128.     property Handle: HMODULE read GetHandle;
  129.     property Loaded: boolean read GetLoaded;
  130.     property Available: boolean read GetAvailable;
  131.     property Count: integer read FCount;
  132.     property EntryName[Index: integer]: string read GetEntryName;
  133.   end;
  134.  
  135.   // The class that keeps a list of all created TDll instances in one place
  136.   TDllNotifyAction = (daLoadedDll, daUnloadedDll, daLinkedRoutine);
  137.   TDllNotifyEvent = procedure(Sender: TDll; Action: TDllNotifyAction; Index: integer) of object;
  138.   TDlls = class(TList)
  139.   private
  140.     FCodeHeap: TCodeHeap;
  141.     FOnDllNotify: TDllNotifyEvent;
  142.     function GetDlls(Index: integer): TDll;
  143.   protected
  144.     procedure DllNotify(Sender: TDll; Action: TDllNotifyAction; Index: integer);
  145.     property CodeHeap: TCodeHeap read FCodeHeap;
  146.   public
  147.     constructor Create;
  148.     destructor Destroy; override;
  149.     property Dlls[Index: integer]: TDll read GetDlls; default;
  150.     property OnDllNotify: TDllNotifyEvent read FOnDllNotify write FOnDllNotify;
  151.   end;
  152.  
  153.   EDllError = class(Exception);
  154.  
  155. var
  156.   Dlls: TDlls;
  157.  
  158. implementation
  159.  
  160. {$IFDEF VER90}
  161. type
  162.   EWin32Error = class(Exception)
  163.   public
  164.     ErrorCode: DWORD;
  165.   end;
  166.  
  167. const
  168.   SWin32Error = 'Win32 Error.  Code: %d.'#10'%s';
  169.   SUnkWin32Error = 'A Win32 API function failed';
  170.  
  171. procedure RaiseLastWin32Error;
  172. var
  173.   LastError: DWORD;
  174.   Error: EWin32Error;
  175. begin
  176.   LastError := GetLastError;
  177.   if LastError <> ERROR_SUCCESS then
  178.     Error := EWin32Error.CreateFmt(SWin32Error, [LastError,
  179.       SysErrorMessage(LastError)])
  180.   else
  181.     Error := EWin32Error.Create(SUnkWin32Error);
  182.   Error.ErrorCode := LastError;
  183.   raise Error;
  184. end;
  185.  
  186. function Win32Check(RetVal: BOOL): BOOL;
  187. begin
  188.   if not RetVal then RaiseLastWin32Error;
  189.   Result := RetVal;
  190. end;
  191. {$ENDIF}
  192.  
  193. function Win32Handle(Handle: THandle): THandle;
  194. begin
  195.   if Handle = 0 then
  196.     RaiseLastWin32Error;
  197.   Result := Handle;
  198. end;
  199.  
  200. function Win32Pointer(P: Pointer): Pointer;
  201. begin
  202.   if P = nil then
  203.     RaiseLastWin32Error;
  204.   Result := P;
  205. end;
  206.  
  207. { TPrivateHeap }
  208.  
  209. constructor TPrivateHeap.Create;
  210. begin
  211.   // Do nothing
  212. end;
  213.  
  214. destructor TPrivateHeap.Destroy;
  215. begin
  216.   if FHandle <> 0 then
  217.   begin
  218.     Win32Check(Windows.HeapDestroy(FHandle));
  219.     FHandle := 0;
  220.   end;
  221.   inherited Destroy;
  222. end;
  223.  
  224. procedure TPrivateHeap.FreeMem(P: pointer);
  225. begin
  226.   Win32Check(Windows.HeapFree(Handle, 0, P));
  227. end;
  228.  
  229. function TPrivateHeap.GetHandle: THandle;
  230. begin
  231.   if FHandle = 0 then
  232.     FHandle := Win32Handle(Windows.HeapCreate(0, 0, 0));
  233.   Result := FHandle;
  234. end;
  235.  
  236. procedure TPrivateHeap.GetMem(var P{: pointer}; Size: DWORD);
  237. begin
  238.   Pointer(P) := Win32Pointer(Windows.HeapAlloc(Handle, AllocationFlags, Size));
  239. end;
  240.  
  241. function TPrivateHeap.SizeOfMem(P: pointer): DWORD;
  242. begin
  243.   Result := Windows.HeapSize(Handle, 0, P);
  244.   // HeapSize does not set GetLastError, but returns $FFFFFFFF if it fails
  245.   if Result = $FFFFFFFF then
  246.     Result := 0;
  247. end;
  248.  
  249. { TCodeHeap }
  250.  
  251. procedure TCodeHeap.GetMem(var P{: pointer}; Size: DWORD);
  252. var
  253.   Dummy: DWORD;
  254. begin
  255.   inherited GetMem(P, Size);
  256.   Win32Check(Windows.VirtualProtect(Pointer(P), Size, PAGE_EXECUTE_READWRITE, @Dummy));
  257. end;
  258.  
  259. {$IFDEF VER90}
  260. const
  261. {$ELSE}
  262. resourcestring
  263. {$ENDIF}
  264.   SIndexOutOfRange      = 'DLL-entry index out of range (%d)';
  265.   SOrdinal              = 'ordinal #';
  266.   SCannotLoadLibrary    = 'Could not find the library: "%s"'#13#10'(%s)';
  267.   SCannotGetProcAddress = 'Could not find the routine "%s" in the library "%s"'#13#10'(%s)';
  268.   SCannotFindThunk      = 'Could not find the TDll object corresponding to the thunk address %p';
  269.  
  270. { Helper routines }
  271.  
  272. function EntryToString(const Entry: TEntry): string;
  273. begin
  274.   if Hi(Entry.ID) <> 0
  275.   then Result := string(Entry.Name)
  276.   else Result := SOrdinal+IntToStr(Entry.ID);
  277. end;
  278.  
  279. procedure ThunkingTarget;
  280. const
  281.   TThunkSize = SizeOf(TThunk);
  282. asm
  283.   // Save register-based parameters
  284.   PUSH    EAX
  285.   PUSH    EDX
  286.   PUSH    ECX
  287. { Stack layout at this point:
  288.   24 [Stack based parameters]
  289.   20 [User code RetAdr]
  290.   16 [Thunk Ret-Adr]
  291.   12 [Self]
  292.    8 [EAX]
  293.    4 [EDX]
  294.    0 [ECX] <-ESP}
  295.   // Get the caller's return address (i.e. one of the thunks)
  296.   MOV     EAX, [ESP+12]   // Self
  297.   MOV     EDX, [ESP+16]   // Thunk
  298.   // The return address is just after the thunk that
  299.   // called us, so go back one step
  300.   SUB     EDX, TYPE TThunk // Using SizeOf(TThunk) here does not work. BASM bug?
  301.   // Do the rest in Pascal
  302.   CALL    TDll.DelayLoadFromThunk{(Self, Thunk);}
  303.   // Now patch the return address on the stack so that we "return" to the DLL routine
  304.   MOV     [ESP+16], EAX
  305.   // Restore register-based parameters
  306.   POP     ECX
  307.   POP     EDX
  308.   POP     EAX
  309.   // Remove the Self pointer!
  310.   ADD        ESP,  4
  311.   // "RETurn" to the DLL!
  312. end;
  313.  
  314. { TDll }
  315.  
  316. constructor TDll.Create(const DllName: string; const Entries: array of TEntry);
  317. begin
  318.   inherited Create;
  319.   FFullPath := DllName;
  320.   FEntries  := @Entries;
  321.   FCount    := High(Entries) - Low(Entries) + 1;
  322.   CreateThunks;
  323.   ActivateThunks;
  324.   Dlls.Add(Self);
  325. end;
  326.  
  327. destructor TDll.Destroy;
  328. begin
  329.   Dlls.Remove(Self);
  330.   Unload;
  331.   DestroyThunks;
  332.   inherited Destroy;
  333. end;
  334.  
  335. procedure TDll.CreateThunks;
  336. const
  337.   CallInstruction = $E8;
  338.   PushInstruction = $68;
  339.   JumpInstruction = $E9;
  340. var
  341.   i : integer;
  342. begin
  343.   // Get a memory block large enough for the thunks
  344.   Dlls.CodeHeap.GetMem(FThunkingCode, SizeOf(TThunkHeader) + SizeOf(TThunk) * Count);
  345.  
  346.   // Generate some machine code in the thunks
  347.   with FThunkingCode^, ThunkHeader do
  348.   begin
  349.     // The per-Dll thunk does this:
  350.     // PUSH    Self
  351.     // JMP     ThunkingTarget
  352.     PUSH   := PushInstruction;
  353.     VALUE  := Self;
  354.     JMP    := JumpInstruction;
  355.     OFFSET := PChar(@ThunkingTarget) - PChar(@Thunks[0]);
  356.     for i := 0 to Count-1 do
  357.       with Thunks[i] do
  358.       begin
  359.         // The per-entry thunk does this:
  360.         // CALL @ThunkingCode^.ThunkHeader
  361.         CALL   := CallInstruction;
  362.         OFFSET := PChar(@FThunkingCode^.ThunkHeader) - PChar(@Thunks[i+1]);
  363.       end;
  364.   end;
  365. end;
  366.  
  367. procedure TDll.DestroyThunks;
  368. begin
  369.   if Assigned(FThunkingCode) then
  370.   begin
  371.     Dlls.CodeHeap.FreeMem(FThunkingCode);
  372.     FThunkingCode := nil;
  373.   end;
  374. end;
  375.  
  376. function TDll.DelayLoadFromThunk(Thunk: PThunk): pointer; register;
  377. begin
  378.   Result := DelayLoadIndex(GetIndexFromThunk(Thunk));
  379. end;
  380.  
  381. function TDll.DelayLoadIndex(Index: integer): pointer;
  382. begin
  383.   Result := GetProcAddrFromIndex(Index);
  384.   FEntries^[Index].Proc^ := Result;
  385. end;
  386.  
  387. class procedure TDll.Error(const Msg: string; Args: array of const);
  388. begin
  389.   raise EDllError.CreateFmt(Msg, Args);
  390. end;
  391.  
  392. function TDll.LoadHandle: HMODULE;
  393. begin
  394.   if FHandle = 0 then
  395.   begin
  396.     FHandle := Windows.LoadLibrary(PChar(FullPath));
  397.     if FHandle <> 0 then
  398.       Dlls.DllNotify(Self, daLoadedDll, -1);
  399.   end;
  400.   Result := FHandle;
  401. end;
  402.  
  403. function TDll.GetHandle: HMODULE;
  404. begin
  405.   Result := FHandle;
  406.   if Result = 0 then
  407.   begin
  408.     Result := LoadHandle;
  409.     if Result = 0 then
  410.       Error(SCannotLoadLibrary, [FullPath, SysErrorMessage(GetLastError)]);
  411.   end;
  412. end;
  413.  
  414. function TDll.GetIndexFromThunk(Thunk: PThunk): integer;
  415. begin
  416.   // We calculate the thunk index by subtracting the start of the array
  417.   // and dividing by the size of the array elements
  418.   Result := (PChar(Thunk) - PChar(@FThunkingCode^.Thunks[0])) div SizeOf(TThunk);
  419. end;
  420.  
  421. function TDll.LoadProcAddrFromIndex(Index: integer; var Addr: pointer): boolean;
  422. begin
  423.   Result := ValidIndex(Index);
  424.   if Result then
  425.   begin
  426.     Addr := Windows.GetProcAddress(Handle, FEntries^[Index].Name);
  427.     Result := Assigned(Addr);
  428.     if Result then
  429.       Dlls.DllNotify(Self, daLinkedRoutine, Index);
  430.   end;
  431. end;
  432.  
  433. function TDll.GetProcAddrFromIndex(Index: integer): pointer;
  434. begin
  435.   if not LoadProcAddrFromIndex(Index, Result) then
  436.     Error(SCannotGetProcAddress, [EntryName[Index], FullPath, SysErrorMessage(GetLastError)]);
  437. end;
  438.  
  439. function TDll.HasThunk(Thunk: PThunk): boolean;
  440. begin
  441.   // The thunk belongs to us if its address is in the thunk array
  442.   Result := (PChar(Thunk) >= PChar(@FThunkingCode^.Thunks[0])) and
  443.             (PChar(Thunk) <= PChar(@FThunkingCode^.Thunks[Count-1]));
  444. end;
  445.  
  446. procedure TDll.Load;
  447. var
  448.   i : integer;
  449. begin
  450.   for i := 0 to Count-1 do
  451.     DelayLoadIndex(i);
  452. end;
  453.  
  454. procedure TDll.SetFullPath(const Value: string);
  455. begin
  456.   if CompareText(FFullPath, Value) <> 0 then
  457.   begin
  458.     Unload;
  459.     FFullPath := Value;
  460.   end;
  461. end;
  462.  
  463. function TDll.GetEntryName(Index: integer): string;
  464. begin
  465.   if ValidIndex(Index)
  466.   then Result := EntryToString(FEntries^[Index])
  467.   else Result := Format(SIndexOutOfRange, [Index]);
  468. end;
  469.  
  470. procedure TDll.ActivateThunks;
  471. // Patch the procedure variables to point to the generated thunks
  472. var
  473.   i : integer;
  474. begin
  475.   for i := 0 to Count-1 do
  476.     FEntries^[i].Proc^ := @FThunkingCode^.Thunks[i];
  477. end;
  478.  
  479. procedure TDll.Unload;
  480. begin
  481.   ActivateThunks;
  482.   if FHandle <> 0 then
  483.   begin
  484.     FreeLibrary(FHandle);
  485.     Dlls.DllNotify(Self, daUnloadedDll, -1);
  486.     FHandle := 0;
  487.   end;
  488. end;
  489.  
  490. function TDll.ValidIndex(Index: integer): boolean;
  491. begin
  492.   Result := (Index >= 0) and (Index <= Count-1);
  493. end;
  494.  
  495. procedure TDll.CheckIndex(Index: integer);
  496. begin
  497.   if not ValidIndex(Index) then
  498.     Error(SIndexOutOfRange, [Index]);
  499. end;
  500.  
  501. function TDll.GetProcs(Index: integer): pointer;
  502. begin
  503.   CheckIndex(Index);
  504.   Result := FEntries^[Index].Proc^;
  505. end;
  506.  
  507. procedure TDll.SetProcs(Index: integer; Value: pointer);
  508. begin
  509.   CheckIndex(Index);
  510.   FEntries^[Index].Proc^ := Value;
  511. end;
  512.  
  513. function TDll.GetAvailable: boolean;
  514. begin
  515.   Result := (LoadHandle <> 0);
  516. end;
  517.  
  518. function TDll.GetLoaded: boolean;
  519. begin
  520.   Result := (FHandle <> 0);
  521. end;
  522.  
  523. function TDll.GetIndexFromProc(Proc: PPointer): integer;
  524. begin
  525.   for Result := 0 to Count-1 do
  526.     if FEntries^[Result].Proc = Proc then
  527.       Exit;
  528.   Result := -1;
  529. end;
  530.  
  531. function TDll.HasRoutine(Proc: PPointer): boolean;
  532. begin
  533.   Result := Available and
  534.             ((not HasThunk(Proc^)) or
  535.               LoadProcAddrFromIndex(GetIndexFromProc(Proc), Proc^));
  536. end;
  537.  
  538. function TDll.HookRoutine(Proc: PPointer; HookProc: Pointer; var OrgProc{: Pointer}): boolean;
  539. begin
  540.   Result := HasRoutine(Proc);
  541.   if Result then
  542.   begin
  543.     Pointer(OrgProc) := Proc^;
  544.     Proc^   := HookProc;
  545.   end;
  546. end;
  547.  
  548. function TDll.UnHookRoutine(Proc: PPointer; var OrgProc{: Pointer}): boolean;
  549. begin
  550.   Result := Assigned(Pointer(OrgProc));
  551.   if Result then
  552.   begin
  553.     Proc^ := Pointer(OrgProc);
  554.     Pointer(OrgProc) := nil;
  555.   end;
  556. end;
  557.  
  558. { TDlls }
  559.  
  560. constructor TDlls.Create;
  561. begin
  562.   inherited Create;
  563.   FCodeHeap := TCodeHeap.Create;
  564. end;
  565.  
  566. destructor TDlls.Destroy;
  567. var
  568.   i : integer;
  569. begin
  570.   for i := Count-1 downto 0 do
  571.     Dlls[i].Free;
  572.   FCodeHeap.Free;
  573.   FCodeHeap := nil;
  574.   inherited Destroy;
  575. end;
  576.  
  577. procedure TDlls.DllNotify(Sender: TDll; Action: TDllNotifyAction; Index: integer);
  578. begin
  579.   if Assigned(FOnDllNotify) then
  580.     FOnDllNotify(Sender, Action, Index);
  581. end;
  582.  
  583. function TDlls.GetDlls(Index: integer): TDll;
  584. begin
  585.   Result := TDll(Items[Index]);
  586. end;
  587.  
  588. initialization
  589.   Dlls := TDlls.Create;
  590. finalization
  591.   Dlls.Free;
  592.   Dlls := nil;
  593. end.
  594.